use crate::core::util::Util;
use std::path::PathBuf;
use std::sync::Mutex;
use tempdir::TempDir;
use tokio::sync::{broadcast, mpsc};
use tracing::info;
use crate::core::services::job::job::JobCommand;

#[derive(Debug)]
pub enum Workspace {
    TempDir(TempDir),
    PathBuf(PathBuf),
}

#[derive(Debug)]
pub struct JobContext {
    workspace: Workspace,

    change_sender: broadcast::Sender<()>,
    change_receiver: broadcast::Receiver<()>,
    log_sender: broadcast::Sender<String>,
    log_receiver: broadcast::Receiver<String>,
    command_sender: mpsc::Sender<JobCommand>,
    command_receiver: Mutex<mpsc::Receiver<JobCommand>>,
}

impl JobContext {
    pub fn new(workspace: Workspace) -> Self {
        let (change_sender, change_receiver) = broadcast::channel(36);
        let (log_sender, log_receiver) = broadcast::channel(36);
        let (command_sender, command_receiver) = mpsc::channel(8);
        JobContext {
            workspace,

            change_sender,
            change_receiver,
            log_sender,
            log_receiver,
            command_sender,
            command_receiver: Mutex::new(command_receiver),
        }
    }

    pub fn path(&self) -> PathBuf {
        match &self.workspace {
            Workspace::TempDir(tmp_dir) => tmp_dir.path().to_path_buf(),
            Workspace::PathBuf(path_buf) => path_buf.clone(),
        }
    }

    pub fn send_change(&self) {
        if let Err(e) = self.change_sender.send(()) {
            Util::log_error(format!("Failed to notify change of job to channel: {e}"));
        }
    }

    pub fn change_receiver(&self) -> broadcast::Receiver<()> {
        self.change_receiver.resubscribe()
    }

    pub fn send_log<S: ToString>(&self, s: S) {
        let s = s.to_string();
        info!(s);
        if let Err(e) = self.log_sender.send(s) {
            Util::log_error(format!("Failed to send log to channel: {e}"));
        }
    }

    pub fn log_receiver(&self) -> broadcast::Receiver<String> {
        self.log_receiver.resubscribe()
    }

    pub async fn send_command(
        &self,
        cmd: JobCommand,
    ) -> Result<(), mpsc::error::SendError<JobCommand>> {
        self.command_sender.send(cmd).await
    }

    pub fn try_get_command(&self) -> Result<JobCommand, mpsc::error::TryRecvError> {
        self.command_receiver.lock().unwrap().try_recv()
    }
}
